/**
 * Copyright (c) iwindplus Technologies Co., Ltd.2024-2030, All rights reserved.
 */

package com.iwindplus.boot.redis;

import cn.hutool.core.text.CharSequenceUtil;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.iwindplus.boot.domain.constant.CommonConstant;
import com.iwindplus.boot.redis.converter.GzipRedisSerializer;
import com.iwindplus.boot.redis.converter.KryoRedisSerializer;
import com.iwindplus.boot.redis.converter.PrefixRedisSerializer;
import com.iwindplus.boot.redis.converter.ProtostuffRedisSerializer;
import com.iwindplus.boot.redis.domain.property.RedisProperty;
import com.iwindplus.boot.redis.handler.ExceptionCacheErrorHandler;
import com.iwindplus.boot.redis.manager.RedissonManager;
import com.iwindplus.boot.redis.manager.SerialNumManager;
import com.iwindplus.boot.redis.strategy.CustomLockFailureStrategy;
import com.iwindplus.boot.redis.strategy.CustomLockKeyBuilder;
import jakarta.annotation.Resource;
import java.util.Arrays;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.BatchStrategies;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.security.jackson2.CoreJackson2Module;

/**
 * Redis配置.
 *
 * @author zengdegui
 * @since 2018/9/5
 */
@Slf4j
@Configuration
@EnableCaching
@EnableConfigurationProperties({CacheProperties.class, RedisProperty.class})
public class RedisConfiguration {

    @Resource
    private RedisConnectionFactory redisConnectionFactory;

    @Resource
    private ReactiveRedisConnectionFactory reactiveRedisConnectionFactory;

    @Resource
    private CacheProperties cacheProperties;

    @Resource
    private RedisProperty property;

    @Resource
    private Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder;

    /**
     * 创建 RedisTemplate.
     *
     * @param keyRedisSerializer   keyRedisSerializer
     * @param valueRedisSerializer valueRedisSerializer
     * @return RedisTemplate<String, Object>
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(PrefixRedisSerializer keyRedisSerializer, GzipRedisSerializer<Object> valueRedisSerializer) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(this.redisConnectionFactory);
        redisTemplate.setKeySerializer(keyRedisSerializer);
        redisTemplate.setValueSerializer(valueRedisSerializer);
        redisTemplate.setHashKeySerializer(keyRedisSerializer);
        redisTemplate.setHashValueSerializer(valueRedisSerializer);
        redisTemplate.afterPropertiesSet();
        log.info("RedisTemplate={}", redisTemplate);
        return redisTemplate;
    }

    /**
     * 创建ReactiveRedisTemplate.
     *
     * @param keyRedisSerializer   keyRedisSerializer
     * @param valueRedisSerializer valueRedisSerializer
     * @return ReactiveRedisTemplate<String, Object>
     */
    @Bean
    public ReactiveRedisTemplate<String, Object> reactiveRedisTemplate(PrefixRedisSerializer keyRedisSerializer,
        GzipRedisSerializer<Object> valueRedisSerializer) {
        RedisSerializationContext<String, Object> serializationContext = RedisSerializationContext.<String, Object>newSerializationContext()
            .key(keyRedisSerializer).value(valueRedisSerializer)
            .hashKey(keyRedisSerializer).hashValue(valueRedisSerializer)
            .build();
        ReactiveRedisTemplate<String, Object> reactiveRedisTemplate = new ReactiveRedisTemplate<>(this.reactiveRedisConnectionFactory,
            serializationContext);
        log.info("ReactiveRedisTemplate={}", reactiveRedisTemplate);
        return reactiveRedisTemplate;
    }

    /**
     * 创建 CacheManager.
     *
     * @param keyRedisSerializer   keyRedisSerializer
     * @param valueRedisSerializer valueRedisSerializer
     * @return CacheManager
     */
    @Bean
    public CacheManager cacheManager(PrefixRedisSerializer keyRedisSerializer, GzipRedisSerializer<Object> valueRedisSerializer) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration
            .defaultCacheConfig()
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keyRedisSerializer))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueRedisSerializer));
        CacheProperties.Redis redisProperties = this.cacheProperties.getRedis();
        if (Objects.nonNull(redisProperties.getTimeToLive())) {
            redisCacheConfiguration = redisCacheConfiguration.entryTtl(redisProperties.getTimeToLive());
        }
        if (CharSequenceUtil.isNotBlank(redisProperties.getKeyPrefix())) {
            redisCacheConfiguration = redisCacheConfiguration.prefixCacheNameWith(redisProperties.getKeyPrefix());
        }
        if (!redisProperties.isCacheNullValues()) {
            redisCacheConfiguration = redisCacheConfiguration.disableCachingNullValues();
        }
        if (!redisProperties.isUseKeyPrefix()) {
            redisCacheConfiguration = redisCacheConfiguration.disableKeyPrefix();
        }
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(this.redisConnectionFactory,
            BatchStrategies.scan(CommonConstant.NumberConstant.NUMBER_ONE_THOUSAND));
        RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisCacheWriter)
            .cacheDefaults(redisCacheConfiguration)
            .build();
        log.info("CacheManager={}", redisCacheManager);
        return redisCacheManager;
    }

    /**
     * 创建 PrefixRedisSerializer.
     *
     * @return PrefixRedisSerializer
     */
    @Bean
    public PrefixRedisSerializer keyRedisSerializer() {
        String keyPrefix = this.cacheProperties.getRedis().isUseKeyPrefix() ? this.cacheProperties.getRedis().getKeyPrefix() : null;
        PrefixRedisSerializer prefixRedisSerializer = new PrefixRedisSerializer(keyPrefix);
        log.info("PrefixRedisSerializer={}", prefixRedisSerializer);
        return prefixRedisSerializer;
    }

    /**
     * 创建 GzipRedisSerializer.
     *
     * @return GzipRedisSerializer<Object>
     */
    @Bean
    public GzipRedisSerializer<Object> valueRedisSerializer() {
        GzipRedisSerializer<Object> serializer = new GzipRedisSerializer<>(this.getRedisSerializer());
        log.info("GzipRedisSerializer={}", serializer);
        return serializer;
    }

    /**
     * 创建 ExceptionCacheErrorHandler.
     *
     * @return ExceptionCacheErrorHandler
     */
    @Bean
    public ExceptionCacheErrorHandler exceptionCacheErrorHandler() {
        ExceptionCacheErrorHandler exceptionCacheErrorHandler = new ExceptionCacheErrorHandler();
        log.info("ExceptionCacheErrorHandler={}", exceptionCacheErrorHandler);
        return exceptionCacheErrorHandler;
    }

    /**
     * 创建 RedissonManager.
     *
     * @return RedissonManager
     */
    @Bean
    public RedissonManager redissonManager() {
        RedissonManager redissonManager = new RedissonManager();
        log.info("RedissonManager={}", redissonManager);
        return redissonManager;
    }

    /**
     * 创建 SerialNumManager.
     *
     * @return SerialNumManager
     */
    @Bean
    public SerialNumManager serialNumManager() {
        SerialNumManager serialNumManager = new SerialNumManager();
        log.info("SerialNumManager={}", serialNumManager);
        return serialNumManager;
    }

    /**
     * 创建 CustomLockFailureStrategy.
     *
     * @return CustomLockFailureStrategy
     */
    @Bean
    public CustomLockFailureStrategy customLockFailureStrategy() {
        CustomLockFailureStrategy customLockFailureStrategy = new CustomLockFailureStrategy();
        log.info("CustomLockFailureStrategy={}", customLockFailureStrategy);
        return customLockFailureStrategy;
    }

    /**
     * 创建 CustomLockKeyBuilder.
     *
     * @return CustomLockKeyBuilder
     */
    @Bean
    public CustomLockKeyBuilder customLockKeyBuilder() {
        CustomLockKeyBuilder customLockKeyBuilder = new CustomLockKeyBuilder();
        log.info("CustomLockKeyBuilder={}", customLockKeyBuilder);
        return customLockKeyBuilder;
    }

    private RedisSerializer getRedisSerializer() {
        switch (this.property.getSerializeType()) {
            case KRYO:
                return new KryoRedisSerializer<>();
            case PROTOSTUFF:
                return new ProtostuffRedisSerializer<>();
            case JDK:
                return new JdkSerializationRedisSerializer();
            default:
                final ObjectMapper objectMapper = jackson2ObjectMapperBuilder.build();
                objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, As.PROPERTY);
                objectMapper.registerModules(Arrays.asList(new CoreJackson2Module()));
                return new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
        }
    }
}
